package im.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
 * Created by gzhd on 2017/12/1.
 */

/**
 * NIO服务端
 *
 * @author yangtao__anxpp.com
 * @version 1.0
 */
public class ServerHandle implements Runnable {
    private Selector selector;
    private ServerSocketChannel serverChannel;
    private volatile boolean started;
    private static final int TIMEOUT = 3000;
    private static final int BUF_SIZE = 1024;

   static public IMUserMap<String, SocketChannel> clients = new IMUserMap<>();

    /**
     * 构造方法
     *
     * @param port 指定要监听的端口号
     */
    public ServerHandle(int port) {
        try {
            //创建选择器
            selector = Selector.open();
            //打开监听通道
            serverChannel = ServerSocketChannel.open();
            //如果为 true，则此通道将被置于阻塞模式；如果为 false，则此通道将被置于非阻塞模式
            serverChannel.configureBlocking(false);//开启非阻塞模式
            //绑定端口 backlog设为1024
            serverChannel.socket().bind(new InetSocketAddress(port), 1024);
            //监听客户端连接请求
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            //标记服务器已开启
            started = true;
            System.out.println("服务器已启动，端口号：" + port);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void stop() {
        started = false;
    }

    @Override
    public void run() {
        //循环遍历selector
        while (started) {
            try {
                //无论是否有读写事件发生，selector每隔1s被唤醒一次
                if (selector.select(TIMEOUT) == 0) {
//                    System.out.println("等待事件来临······" + new Date());
                    continue;
                }
                //阻塞,只有当至少一个注册的事件发生的时候才会继续.
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
//                        处理接收到的KEY
                        handleInput(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
        //selector关闭后会自动释放里面管理的资源
        if (selector != null)
            try {
                selector.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

    private void handleInput(SelectionKey key) throws Exception {
//           在这里做分发
        if (key.isAcceptable()) {
            System.out.println("isAcceptable事件");
            handleAccept(key);
        }
        if (key.isReadable()) {
            System.out.println("isReadable事件");
            handleRead(key);
        }
        if (key.isWritable() && key.isValid()) {
            System.out.println("isWritable事件");
            handleWrite(key);
        }
        if (key.isConnectable()) {
//            handleConnectable(key);
            System.out.println("isConnectable = true  连接成功！");
        }
    }

    public void handleAccept(SelectionKey key) throws IOException {
        ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
        SocketChannel sc = ssChannel.accept();
        sc.configureBlocking(false);
        sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocateDirect(BUF_SIZE));
    }


    private void handleRead(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(BUF_SIZE);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        handleMsg(channel, msg);
    }

    /**
     * 处理不同的消息
     *
     * @param channel
     * @param msg     收到的信息
     */
    private void handleMsg(SocketChannel channel, String msg) throws Exception {
//        处理登录信息
        if (msg.startsWith(IMMsgProtocol.USER_ROUND) && msg.endsWith(IMMsgProtocol.USER_ROUND)) {
            handleLogin(channel, msg);
        }
        // 如果读到的行以CrazyitProtocol.PRIVATE_ROUND开始，并以其结束，
        // 可以确定是私聊信息，私聊信息只向特定的输出流发送
        //  私信格式:   //user:XXXXX
        else if (msg.startsWith(IMMsgProtocol.PRIVATE_ROUND) && msg.endsWith(IMMsgProtocol.PRIVATE_ROUND)) {
            handleP2PMsg(channel, msg);
        }
        // 公聊要向每个Socket发送
        else {
            handleP2AllMsg(channel, msg);
        }
    }

    /**
     * 处理群聊信息
     *
     * @param channel
     * @param msg
     */
    private void handleP2AllMsg(SocketChannel channel, String msg) throws Exception {
        // 得到真实消息
        msg = getRealMsg(msg);
        System.out.println("取得信息" + msg);
        // 遍历clients中的每个输出流
//        for (PrintStream clientPs : fk.Senior.server.Server.clients.valueSet()) {
//            clientPs.println(Server.clients.getKeyByValue(ps) + "说：" + msg);
//        }
        String user = clients.getKeyByValue(channel);
        byte[] bytes = (user+IMMsgProtocol.SEND_MYSELF+"说:"+msg).getBytes();
        //根据数组容量创建ByteBuffer
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        //将字节数组复制到缓冲区
        writeBuffer.put(bytes);
        for (SocketChannel sc : clients.valueSet()) {
            //flip操作
            writeBuffer.flip();
            sc.write(writeBuffer);
        }
    }

    /**
     * 处理私信信息
     *
     * @param channel
     * @param msg
     */
    private void handleP2PMsg(SocketChannel channel, String msg) throws Exception {
        // 得到真实消息
        String userAndMsg = getRealMsg(msg);
        // 以SPLIT_SIGN分割字符串，前半是私聊用户，后半是聊天信息
        String user = userAndMsg.split(IMMsgProtocol.SPLIT_SIGN)[0];
        String msginfo = userAndMsg.split(IMMsgProtocol.SPLIT_SIGN)[1];
        // 获取私聊用户对应的输出流，并发送私聊信息
        String sendMsg = clients.getKeyByValue(channel) + "悄悄地对你说：" + msginfo;
        System.out.println(sendMsg);
        byte[] bytes = sendMsg.getBytes();
        //根据数组容量创建ByteBuffer
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        //将字节数组复制到缓冲区
        writeBuffer.put(bytes);
        //flip操作
        writeBuffer.flip();
        //发送缓冲区的字节数组
        clients.map.get(user).write(writeBuffer);

//        clients.map.get(user).write();
//        fk.Senior.server.Server.clients.map.get(user)
//                .println(fk.Senior.server.Server.clients.getKeyByValue(ps) + "悄悄地对你说：" + msg);

    }

    /**
     * 处理登录信息
     *
     * @param channel
     * @param msg
     */
    private void handleLogin(SocketChannel channel, String msg) throws Exception {
        String resultMsg = "";
        ByteBuffer outbuffer = null;
        // 得到真实消息
        String userName = getRealMsg(msg);
        // 如果用户名重复
        if (fk.Senior.server.Server.clients.map.containsKey(userName)) {
            System.out.println("重复");
            resultMsg = IMMsgProtocol.NAME_REP;
        } else {
            System.out.println(userName + "成功加入！");
            resultMsg = IMMsgProtocol.LOGIN_SUCCESS;
//            加入队
            clients.put(userName, channel);
//          fk.Senior.server.Server.clients.put(userName, ps);
        }
        outbuffer = ByteBuffer.wrap(resultMsg.getBytes());
        channel.write(outbuffer);
    }

    public void handleWrite(SelectionKey key) throws IOException {
        ByteBuffer buf = (ByteBuffer) key.attachment();
        buf.flip();
        SocketChannel sc = (SocketChannel) key.channel();
        while (buf.hasRemaining()) {
            sc.write(buf);
        }
        buf.compact();
    }


    public void handleConnectable(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        if (channel.isConnectionPending()) {
            channel.finishConnect();
        }
        channel.configureBlocking(false);
        channel.write(ByteBuffer.wrap("hello from cliteen".getBytes()));
        channel.register(this.selector, SelectionKey.OP_READ);
    }

    //异步发送应答消息
    private void doWrite(SocketChannel channel, String response) throws IOException {
        //将消息编码为字节数组
        byte[] bytes = response.getBytes();
        //根据数组容量创建ByteBuffer
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        //将字节数组复制到缓冲区
        writeBuffer.put(bytes);
        //flip操作
        writeBuffer.flip();
        //发送缓冲区的字节数组
        channel.write(writeBuffer);
        //****此处不含处理“写半包”的代码
    }

    private String getRealMsg(String line) {
        return line.substring(IMMsgProtocol.PROTOCOL_LEN, line.length() - IMMsgProtocol.PROTOCOL_LEN);
    }
}